﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Configuration;
using System.Collections;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using System.IO;

namespace BIM.XML3DExporter
{

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Automatic)]
    public class cmdConvertTo3DXML : IExternalCommand
    {
        private readonly XML3DGenerator generator = new XML3DGenerator();

        private string xml3DfoldPath = null;

        private int elementIdx = 0;

        void InitialData()
        {
            elementIdx = 1;
            string AddInPath = BIM.XML3DExporter.XML3DExportUI.AddInPath;
            string directoryName = Path.GetDirectoryName(AddInPath);
            xml3DfoldPath = directoryName + @"\3DXML";
            if (!Directory.Exists(xml3DfoldPath))
            {
                Directory.CreateDirectory(xml3DfoldPath);
            }
        }

        public bool GetSolidFacetInfo(Solid solid, out String triangleIdxListStr,
            out String vertexPositionListStr)
        {
            bool suc = false;
            triangleIdxListStr = null;
            vertexPositionListStr = null;
            if (!SolidUtils.IsValidForTessellation(solid))
            {
                return suc;
            }

            SolidOrShellTessellationControls tessControl = new SolidOrShellTessellationControls();
            tessControl.LevelOfDetail = 0.5;

            TriangulatedSolidOrShell shellData = SolidUtils.TessellateSolidOrShell(solid, tessControl);
            int shellNum = shellData.ShellComponentCount;
            for (int i = 0; i < shellNum; ++i )
            {
                TriangulatedShellComponent shellComponent = shellData.GetShellComponent(i);
                int triangleCount = shellComponent.TriangleCount;
                int vertetxCount = shellComponent.VertexCount;
                //Test
                for (int j = 0; j < triangleCount; ++j )
                {
                    TriangleInShellComponent curTriangle = shellComponent.GetTriangle(j);
                    if (i == 0 && j == 0)
                    {
                        triangleIdxListStr += String.Format("{0} {1} {2}", curTriangle.VertexIndex0,
                            curTriangle.VertexIndex1, curTriangle.VertexIndex2);
                    }
                    else
                    {
                        triangleIdxListStr += String.Format(" {0} {1} {2}", curTriangle.VertexIndex0,
                            curTriangle.VertexIndex1, curTriangle.VertexIndex2);
                    }
                }
                for (int k = 0; k < vertetxCount; ++k )
                {
                    XYZ curVeretx = shellComponent.GetVertex(k);
                    if (i == 0 && k == 0)
                    {
                        vertexPositionListStr += String.Format("{0} {1} {2}", curVeretx.X,
                            curVeretx.Y, curVeretx.Z);
                    }
                    else
                    {
                        vertexPositionListStr += String.Format(" {0} {1} {2}", curVeretx.X,
                            curVeretx.Y, curVeretx.Z);
                    }
                }
            }
            if (triangleIdxListStr != "" && vertexPositionListStr != "")
            {
                suc = true;
            }
            return suc;
        }

        private void GetElementFacetInfo(Element curElement)
        {
            GeometryElement geomElement = curElement.get_Geometry(new Options());

            foreach (GeometryObject geomObj in geomElement)
            {
                // Get the geometry instance which contains the geometry information
                //GeometryInstance instance = geomObj as GeometryInstance;
                //if (null == instance)
                //{
                //    continue;
                //}
                Solid solid = geomObj as Solid;

                if (null == solid || 0 == solid.Faces.Size ||
                    0 == solid.Edges.Size)
                {
                    continue;
                }
                String faceStrList;
                String vertexStrList;
                GetSolidFacetInfo(solid, out faceStrList, out vertexStrList);
                generator.facesListString = faceStrList;
                generator.vertexListString = vertexStrList;
                String fileName = String.Format(@"\WH_{0}.3DRep", elementIdx++);
                generator.Generate3DRepFile(xml3DfoldPath + fileName);
                //generator.Generate3DRepFile(@"D:\WH_1.3DRep");
                //Transform instTransform = instance.Transform;
                // Get the faces and edges from solid, and transform the formed points
                foreach (Face face in solid.Faces)
                {
                    Mesh mesh = face.Triangulate();
                    foreach (XYZ ii in mesh.Vertices)
                    {
                        XYZ point = ii;
                        //XYZ transformedPoint = instTransform.OfPoint(point);
                    }
                }
                foreach (Edge edge in solid.Edges)
                {
                    foreach (XYZ ii in edge.Tessellate())
                    {
                        XYZ point = ii;
                        //XYZ transformedPoint = instTransform.OfPoint(point);
                    }
                }

            }
        }

        private void GetFacesAndEdgesOfWall(Wall wall)
        {
            String faceInfo = "";

            Autodesk.Revit.DB.Options opt = new Options();
            Autodesk.Revit.DB.GeometryElement geomElem = wall.get_Geometry(opt);
            foreach (GeometryObject geomObj in geomElem)
            {
                Solid geomSolid = geomObj as Solid;
                if (null != geomSolid)
                {
                    int faces = 0;
                    double totalArea = 0;
                    foreach (Face geomFace in geomSolid.Faces)
                    {
                        faces++;
                        faceInfo += "Face " + faces + " area: " + geomFace.Area.ToString() + "\n";
                        totalArea += geomFace.Area;

                        // Get mesh
                        Mesh mesh = geomFace.Triangulate();
                        for (int i = 0; i < mesh.NumTriangles; i++)
                        {
                            MeshTriangle triangle = mesh.get_Triangle(i);
                            XYZ vertex1 = triangle.get_Vertex(0);
                            XYZ vertex2 = triangle.get_Vertex(1);
                            XYZ vertex3 = triangle.get_Vertex(2);
                        }

                    }
                    faceInfo += "Number of faces: " + faces + "\n";
                    faceInfo += "Total area: " + totalArea.ToString() + "\n";
                    foreach (Edge geomEdge in geomSolid.Edges)
                    {
                        // get wall's geometry edges
                        IList<XYZ> mesh = geomEdge.Tessellate();
                        foreach (XYZ vertex in mesh)
                        {
                            XYZ curVertex = vertex;
                        }
                    }
                }
            }
            TaskDialog.Show("Revit", faceInfo);
        }

        #region IExternalCommand Members

        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            try
            {
                InitialData();

                UIApplication app = commandData.Application;

                Document doc = app.ActiveUIDocument.Document;

                Transaction trans = new Transaction(doc, "ExComm");

                // Select some elements in Revit before invoking this command
                // Get the handle of current document.
                UIDocument uidoc = commandData.Application.ActiveUIDocument;
                // Get the element selection of current document.
                Selection selection = uidoc.Selection;
                ElementSet collection = selection.Elements;
                if (0 == collection.Size)
                {
                    // If no elements selected.
                    //TaskDialog.Show("Revit", "请选择至少一个构件进行信息查询。");
                }
                else
                {
                    String info = "所选择的构件信息列表如下: ";
                    int i = 0;
                    //String temp;
                    foreach (Element elem in collection)
                    {
                        i++;
                        info += "\n\t" + "名称:" + elem.Name;

                        Wall curWall = elem as Wall;
                        WallType curType = curWall.WallType;
                        string wallStr = curType.Kind.ToString();
                        if (curWall != null)
                        {
                            Utility util = new Utility();
                            XYZ wallDimension = util.GetWallDimensions(doc, curWall);
                            info += "\n\t" + "墙体长度：" + wallDimension.X.ToString() + "mm\n\t"
                                + "墙体宽度：" + wallDimension.Y.ToString() + "mm\n\t"
                                + "墙体高度：" + wallDimension.Z.ToString() + "mm";

                            //facet info
                            //GetFacesAndEdgesOfWall(curWall);
                        }
                        GetElementFacetInfo(elem);
                    }
                    TaskDialog.Show("构件信息", info);
                }

                string fileName = XMLDialogManager.SaveDialog();

                if (!string.IsNullOrEmpty(fileName))
                {

                    bool suc = generator.Create3DXMLFile(fileName);
                    if (suc)
                    {
                        TaskDialog.Show("Revit", "导出3DXML文件成功。");
                    }
                    else
                    {
                        TaskDialog.Show("Revit", "导出3DXML文件失败。");
                    }
                }

            }
            catch (Exception e)
            {
                message = e.Message;
                return Autodesk.Revit.UI.Result.Failed;
            }
            return Result.Succeeded;

            throw new NotImplementedException();
        }

        #endregion

    }

    class Utility
    {
        public XYZ GetWallDimensions(Document doc, Wall wall)
        {

            //read the wall's name

            string wallName = wall.Name;



            //read the wall's length by reading parameter.            

            LocationCurve lc = wall.Location as LocationCurve;

            double dWallLength = lc.Curve.Length;



            //get wall base absoutely height

            Parameter paramBottomLevel = wall.get_Parameter(BuiltInParameter.WALL_BASE_CONSTRAINT); //base level.

            //get the bottom level id

            ElementId idbottom = paramBottomLevel.AsElementId();

            Level levelBottom = doc.GetElement(idbottom) as Level;

            Parameter paramBotomOffset = wall.get_Parameter(BuiltInParameter.WALL_BASE_OFFSET); //base offset.

            double dBottomHeight = levelBottom.Elevation + paramBotomOffset.AsDouble();


            //get wall top absoutely height

            double dHeight = 0;

            Parameter paramTopLevel = wall.get_Parameter(BuiltInParameter.WALL_HEIGHT_TYPE); //top level.

            if (paramTopLevel != null)
            {

                //get the bottom level id

                ElementId idTop = paramTopLevel.AsElementId();

                Level levelTop = doc.GetElement(idTop) as Level;

                if (levelTop != null)
                {

                    Parameter paramTopOffset = wall.get_Parameter(BuiltInParameter.WALL_TOP_OFFSET); //upper offset.

                    double dHeightTop = levelTop.Elevation + paramTopOffset.AsDouble();



                    //wall height

                    dHeight = dHeightTop - dBottomHeight;

                }

                else

                    dHeight = wall.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM).AsDouble();

            }

            //get wall width

            double dWidth = wall.Width;

            dWallLength *= 304.8;
            dWidth *= 304.8;
            dHeight *= 304.8;
            return new XYZ(dWallLength, dWidth, dHeight);

        }
    }
}
